package com.sifang.POI.dianping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.Session;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;

public class AddCategoryRegionCommand extends MultiThreadsCommand {
	
	private int cityId;
	
	private Map<String, String> categoryMap;
	private Map<String, String> regionMap;
	private int counter;
	
	
	public AddCategoryRegionCommand(int cityId) {
		this.setCityId(cityId);
		this.categoryMap = new HashMap<String, String>();
		this.regionMap = new HashMap<String, String>();
	}

	private void updateStatus(String url) {
		String status = String.format("%d | %s", incProcessed(), getElapseTimeString());
		status = String.format("\r%-10s%40s", url.substring(url.lastIndexOf('/') + 1), status);
		System.out.print(status);
	}
	
	private synchronized int incProcessed() {
		return ++this.counter;
	}

	@Override
	public void execute() throws Exception {
		if (!isTablesInitialized()) {
			System.out.println("\ninitialize tables...");
			initializeTables();
			persistTables();
			String status = String.format("\ncategory:%d, region:%d",
					categoryMap.size(), regionMap.size());
			System.out.println(status);
		}
		super.execute();
		reuseCategoryMap();
		reuseRegionMap();
		appendCategoryRegion();
	}
	
	@SuppressWarnings("unchecked")
	private void reuseCategoryMap() {
		Session s = DpDb.getInstance().openSession();
		String query = String.format("from Category category where category.cityId = %d", getCityId());
		List<Category> results = s.createQuery(query).list();
		for (Category result : results) {
			categoryMap.put(result.getUrl(), result.getPath());
		}
	}
	
	@SuppressWarnings("unchecked")
	private void reuseRegionMap() {
		Session s = DpDb.getInstance().openSession();
		String query = String.format("from Region region where region.cityId = %d", getCityId());
		List<Region> results = s.createQuery(query).list();
		for (Region result : results) {
			regionMap.put(result.getUrl(), result.getPath());
		}
	}

	@SuppressWarnings("unchecked")
	private void appendCategoryRegion() {
		int start = 0;
		String query = String.format("from Shop shop where shop.cityId = %d", getCityId());
		Session s = DpDb.getInstance().openSession();
		while (true) {
			List<Shop> shops = s.createQuery(query).setFirstResult(start).setMaxResults(getDbReadBatchSize()).list();
			start += shops.size();
			
			if (shops.size() == 0)
				break;
			
			s.beginTransaction();
			for (Shop shop : shops) {
				String tagsString = shop.getTags();
				String[] tags = tagsString.split(",");
				StringBuilder sbCategory = new StringBuilder();
				StringBuilder sbRegion = new StringBuilder();
				for (String tag : tags) {
					int index = tag.indexOf(':');
					String sectionUrl = tag.substring(index + 1);
					index = sectionUrl.lastIndexOf('/');
					char c = sectionUrl.charAt(index + 1);
					if (c == 'g') {
						sbCategory.append(categoryMap.get(sectionUrl)).append(',');
					} else if (c == 'r') {
						sbRegion.append(regionMap.get(sectionUrl)).append(',');
					}
				}
				if (sbCategory.length() > 0)
					sbCategory.deleteCharAt(sbCategory.length() - 1);
				if (sbRegion.length() > 0)
					sbRegion.deleteCharAt(sbRegion.length() - 1);
				shop.setCategory(sbCategory.toString());
				shop.setRegion(sbRegion.toString());
				s.update(shop);
				
			}
			s.getTransaction().commit();
			System.out.print(String.format("\r%d shops processed", start));
		}
		s.close();
	}

	private void persistTables() {
		Session s = DpDb.getInstance().openSession();
		s.beginTransaction();
		for (Entry<String, String> item : categoryMap.entrySet()) {
			s.save(new Category(item.getKey(), item.getValue(), null, getCityId()));
		}
		for (Entry<String, String> item : regionMap.entrySet()) {
			s.save(new Region(item.getKey(), item.getValue(), null, getCityId()));
		}
		s.getTransaction().commit();
		s.close();
	}

	@SuppressWarnings("unchecked")
	private void initializeTables() {
		int start = 0;
		String query = String.format("from Shop shop where shop.cityId = %d", getCityId());
		Session s = DpDb.getInstance().openSession();
		while (true) {
			List<Shop> shops = s.createQuery(query).setFirstResult(start).setMaxResults(getDbReadBatchSize()).list();
			start += getDbReadBatchSize();
			System.out.print(String.format("\r%d shops checked", start));
			if (shops.size() == 0)
				break;
			for (Shop shop : shops) {
				String tagsString = shop.getTags();
				String[] tags = tagsString.split(",");
				for (String tag : tags) {
					int index = tag.indexOf(':');
					String sectionName = tag.substring(0, index);
					String sectionUrl = tag.substring(index + 1);
					index = sectionUrl.lastIndexOf('/');
					char c = sectionUrl.charAt(index + 1);
					if (c == 'g' && !categoryMap.containsKey(sectionUrl)) {
						categoryMap.put(sectionUrl, sectionName);
					} else if (c == 'r' && !regionMap.containsKey(sectionUrl)) {
						regionMap.put(sectionUrl, sectionName);
					}
				}
			}
		}
		s.close();
	}

	private boolean isTablesInitialized() {
		String query = String.format("from Category category where category.cityId = %d", getCityId());
		Session s = DpDb.getInstance().openSession();
		Object result = s.createQuery(query).setMaxResults(1).uniqueResult();
		s.close();
		return result != null;
	}
	
	private String getCategoryPath(Category category) throws IOException, XPatherException {
		TagNode node = WebUtil.cleanUrl(category.getUrl());
		Object[] results = node.evaluateXPath("//div[@class='asideContainer']//a[@class='D arrow']");
		if (results.length < 1) {
			System.out.println("\ncan't get category path of " + category.getUrl());
			return null;
		}
		StringBuilder sb = new StringBuilder();
		for (int i = 1; i < results.length; i++) {
			if (sb.length() > 0) {
				sb.append('-');
			}
			node = (TagNode) results[i];
			String value = node.getText().toString();
			value = value.substring(value.lastIndexOf(';') + 1);
			sb.append(value);
			String nodeUrl = WebUtil.getFullUrl(node.getAttributeByName("href"));
			synchronized (this) {
				Session s = DpDb.getInstance().openSession();
				s.beginTransaction();
				s.saveOrUpdate(new Category(nodeUrl, value, null, getCityId()));
				s.getTransaction().commit();
				s.close();
			}
		}
		if (sb.length() > 0) {
			sb.append('-');
		}
		sb.append(category.getName());
		return sb.toString();
	}
	
	private String getRegionPath(Region region) throws IOException, XPatherException {
		TagNode node = WebUtil.cleanUrl(region.getUrl());
		Object[] results = node.evaluateXPath("//div[@class='asideContainer']//a[@class='D arrow']");
		if (results.length < 1) {
			System.out.println("\ncan't get region path of " + region.getUrl());
			return null;
		}
		StringBuilder sb = new StringBuilder();
		for (int i = 1; i < results.length; i++) {
			if (sb.length() > 0) {
				sb.append('-');
			}
			node = (TagNode) results[i];
			String value = node.getText().toString();
			value = value.substring(value.lastIndexOf(';') + 1);
			sb.append(value);
			String nodeUrl = WebUtil.getFullUrl(node.getAttributeByName("href"));
			synchronized (this) {
				Session s = DpDb.getInstance().openSession();
				s.beginTransaction();
				s.saveOrUpdate(new Region(nodeUrl, value, null, getCityId()));
				s.getTransaction().commit();
				s.close();
			}
		}
		if (sb.length() > 0) {
			sb.append('-');
		}
		sb.append(region.getName());
		return sb.toString();
	}
	
	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		new AddCategoryRegionCommand(288).execute();
	}

	public int getCityId() {
		return cityId;
	}

	public void setCityId(int cityId) {
		this.cityId = cityId;
	}
	
	@Override
	@SuppressWarnings("unchecked")
	protected List<? extends Runnable> getNextBatchWorks() {
		Session s = DpDb.getInstance().openSession();
		String query = String.format("from Category category where category.path = null and category.cityId = %d", getCityId());
		List<Category> categories = s.createQuery(query).setMaxResults(getDbReadBatchSize()).list();
		s.close();
		if (categories.size() > 0) {
			List<CategoryWorkitem> categoryWorks = new ArrayList<CategoryWorkitem>();
			for (Category category : categories) {
				categoryWorks.add(new CategoryWorkitem(category));
			}
			return categoryWorks;
		} else {
			s = DpDb.getInstance().openSession();
			query = String.format("from Region region where region.path = null and region.cityId = %d", getCityId());
			List<Region> regions = s.createQuery(query).setMaxResults(getDbReadBatchSize()).list();
			s.close();
			List<RegionWorkitem> regionWorks = new ArrayList<RegionWorkitem>();
			for (Region region : regions) {
				regionWorks.add(new RegionWorkitem(region));
			}
			return regionWorks;
		}
	}
	
	class CategoryWorkitem implements Runnable {
		
		private Category category;

		public CategoryWorkitem(Category category) {
			this.category = category;
		}

		@Override
		public void run() {
			String path = null;
			try {
				path = getCategoryPath(category);
			} catch (Exception e) {
				e.printStackTrace();
				return;
			}
			Session s = DpDb.getInstance().openSession();
			s.beginTransaction();
			category.setPath(path);
			s.update(category);
			s.getTransaction().commit();
			s.close();
			updateStatus(category.getUrl());
		}
	}
	
	class RegionWorkitem implements Runnable {
		
		private Region region;

		public RegionWorkitem(Region region) {
			this.region = region;
		}

		@Override
		public void run() {
			String path = null;
			try {
				path = getRegionPath(region);
			} catch (Exception e) {
				e.printStackTrace();
				return;
			}
			Session s = DpDb.getInstance().openSession();
			s.beginTransaction();
			region.setPath(path);
			s.update(region);
			s.getTransaction().commit();
			s.close();
			updateStatus(region.getUrl());
		}
	}

}
